﻿using ScannerClasses;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ParserClasses.Classes
{
    public class Parser : IParser
    {
        private IScanner _scanner;
        private string _logs;
        private Program _program;
        public Parser(IScanner scanner)
        {
            _scanner = scanner;
        }

        public Parser()
        {

        }

        public void ParseProgram(IScanner scanner)
        {
            if (scanner.HasToken(TokenTypes.TokenType.MAIN))
            {
                _program = new Program();
                while (_scanner.HasNext())
                {
                    Function func = ParseFunction();
                    _program.AddFunction(func);
                }
            }
            else
            {
                Logs += "Token Main not detected";
            }
        }

        private Function ParseFunction()
        {
            AcceptToken(TokenTypes.TokenType.FUNCTION);
            Function function = new Function();
            IToken returnType = AcceptToken(TokenTypes.TokenType.VOID, TokenTypes.TokenType.INT, TokenTypes.TokenType.DOUBLE, TokenTypes.TokenType.WORD, TokenTypes.TokenType.BOOLEAN);
            function.ReturnType = returnType.Type;

            IToken spelling = AcceptToken(TokenTypes.TokenType.IDENTIFIER);
            function.Spelling = spelling.Spelling;

            ArgumentList arguments = ParseArgumentList();
            function.Arguments = arguments.GetAll();

            StatementList statements = ParseStatementList();
            function.Statements = statements.GetAll();

            return function;
        }

        private Expression ParseExpression()
        {
            Primary fprim = ParsePrimary();
            Expression expression = new Expression();
            expression.addToExpressionPrimaryList(fprim);

            while (CorrectToken(PeekToken(), TokenTypes.TokenType.OPERATOR))
            {
                expression.addToExpressionOperatorsToken(AcceptToken(TokenTypes.TokenType.OPERATOR));
                Primary sprim = ParsePrimary();
                expression.addToExpressionPrimaryList(sprim);

            }
            return expression;

        }

        private Primary ParsePrimary()
        {
            if (CorrectToken(PeekToken(), TokenTypes.TokenType.IDENTIFIER))
            {
                Primary primary = new Primary();
                primary.AddPrimaryToken(AcceptToken(TokenTypes.TokenType.IDENTIFIER));
                return primary;
            }
            else
                if (CorrectToken(PeekToken(), TokenTypes.TokenType.OPERATOR))
                {
                    Primary primary = new Primary();
                    primary.AddPrimaryToken(AcceptToken(TokenTypes.TokenType.OPERATOR));
                    return primary;
                }
                else
                    if (CorrectToken(PeekToken(), TokenTypes.TokenType.INTEGERLITERAL))
                    {
                        Primary primary = new Primary();
                        primary.AddPrimaryToken(AcceptToken(TokenTypes.TokenType.INTEGERLITERAL));
                        return primary;
                    }
                    else
                        if (CorrectToken(PeekToken(), TokenTypes.TokenType.WORDLITERAL))
                        {
                            Primary primary = new Primary();
                            primary.AddPrimaryToken(AcceptToken(TokenTypes.TokenType.WORDLITERAL));
                            return primary;
                        }
                        else
                            if (CorrectToken(PeekToken(), TokenTypes.TokenType.TRUE) || CorrectToken(PeekToken(), TokenTypes.TokenType.FALSE))
                            {
                                Primary primary = new Primary();
                                primary.AddPrimaryToken(AcceptToken(TokenTypes.TokenType.FALSE, TokenTypes.TokenType.TRUE));
                                return primary;
                            }
                            else
                                if (CorrectToken(PeekToken(), TokenTypes.TokenType.LEFTPARAN))
                                {
                                    Primary primary = new Primary();
                                    primary.AddPrimaryToken(AcceptToken(TokenTypes.TokenType.FALSE, TokenTypes.TokenType.LEFTPARAN));
                                    Expression myExpression = ParseExpression();
                                    primary.AddPrimaryToken(AcceptToken(TokenTypes.TokenType.RIGHTPARAN));
                                    return primary;
                                }
                                else
                                {
                                    throw new ArgumentException("Explain yourself in primary");
                                }

        }

        private StatementList ParseStatementList()
        {
            AcceptToken(TokenTypes.TokenType.LEFTCURLYBRACE);
            StatementList list = new StatementList();
            while (CorrectToken(PeekToken(), TokenTypes.TokenType.NEW) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.IDENTIFIER) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.OPERATOR) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.INTEGERLITERAL) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.DOUBLELITERAL) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.WORDLITERAL) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.IF) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.WHILE) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.FOR) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.CR) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.CW) ||
                  CorrectToken(PeekToken(), TokenTypes.TokenType.LEFTPARAN))
            {
                Statement st = ParseStatement();
                list.AddStatement(st);
            }

            AcceptToken(TokenTypes.TokenType.RIGHTCURLYBRACE);
            return list;
        }

        private Statement ParseStatement()
        {
            Statement stmt = new Statement();
            if (CorrectToken(PeekToken(), TokenTypes.TokenType.NEW))
            {
                IToken newtoken = AcceptToken(TokenTypes.TokenType.NEW);
                stmt.AddToken(newtoken);
                IToken typetoken = AcceptToken(TokenTypes.TokenType.INT, TokenTypes.TokenType.DOUBLE, TokenTypes.TokenType.WORD, TokenTypes.TokenType.BOOLEAN);
                stmt.AddToken(typetoken);
                IToken identifier = AcceptToken(TokenTypes.TokenType.IDENTIFIER);
                stmt.AddToken(identifier);
                if (CorrectToken(PeekToken(), TokenTypes.TokenType.SEMICOLON))
                {
                    IToken end = AcceptToken(TokenTypes.TokenType.SEMICOLON);
                    stmt.AddToken(end);
                    return stmt;
                }
                else if (CorrectToken(PeekToken(), TokenTypes.TokenType.ASSIGN))
                {
                    IToken assigntoken = AcceptToken(TokenTypes.TokenType.ASSIGN);
                    stmt.AddToken(assigntoken);

                }
            }

            return null;
        }
        private ArgumentList ParseArgumentList()
        {
            AcceptToken(TokenTypes.TokenType.LEFTPARAN);
            ArgumentList list = new ArgumentList();
            Argument arg1 = ParseArgument();
            list.AddArgument(arg1);
            while (CorrectToken(PeekToken(), TokenTypes.TokenType.COMMA))
            {
                AcceptToken(TokenTypes.TokenType.COMMA);
                Argument args = ParseArgument();
                list.AddArgument(args);
            }
            AcceptToken(TokenTypes.TokenType.RIGHTPARAN);

            return list;
        }

        private Argument ParseArgument()
        {
            Argument arg = new Argument();
            IToken type = AcceptToken(TokenTypes.TokenType.INT, TokenTypes.TokenType.DOUBLE, TokenTypes.TokenType.WORD, TokenTypes.TokenType.BOOLEAN);
            arg.Type = type.Type;
            IToken spelling = AcceptToken(TokenTypes.TokenType.IDENTIFIER);
            arg.Spelling = spelling.Spelling;
            return arg;
        }

        public IToken PeekToken()
        {
            IToken token = _scanner.Peek();

            return token;

        }
        public IToken AcceptToken(TokenTypes.TokenType type)
        {
            IToken token = _scanner.Scan();
            if (CorrectToken(token, type))
            {
                return token;
            }
            else
            {
                _logs += string.Format("Expected Token Type: {0} Recieved Type: {1}", type, token.Type);
                throw new ArgumentException("Token tyoe was not valid ");
            }
        }
        public IToken AcceptToken(params TokenTypes.TokenType[] type)
        {
            IToken token = _scanner.Scan();
            if (CorrectToken(token, type))
            {
                return token;
            }
            else
            {
                _logs += string.Format("Expected Token Type: {0} Recieved Type: {1}", type, token.Type);
                throw new ArgumentException("Token tyoe was not valid ");
            }
        }

        private bool CorrectToken(IToken token, params TokenTypes.TokenType[] type)
        {
            return type.Any(t => t == token.Type);
        }
        private bool CorrectToken(IToken token, TokenTypes.TokenType type)
        {
            return token.Type == type;
        }

        public async void ParseProgramAsync(IScanner scanner)
        {
            await Task.Run(() => ParseProgram(scanner));
        }
        public async void ParseProgramAsync()
        {
            await Task.Run(() => ParseProgram());
        }

        public string Logs
        {
            get
            {
                return _logs;
            }
            set
            {
                _logs = value;
            }
        }


        public void ParseProgram()
        {
            ParseProgram(_scanner);
        }
    }
}
